/*---------------------------------------------------------------------------*\
|* This file is part of the koala project with MIT License.                  *|
|* Copyright (c) James <zhuguangxiang@gmail.com>                             *|
\*--------------------------------------------------------------------------**/

#include "ir/ir.h"
#include "ir/printer.h"
#include "ir/ssa.h"
#include "util/hash.h"
#include "util/hashmap.h"
#include "util/list.h"
#include "util/log.h"
#include "util/vector.h"

/*
func foo(a int, b int) int {
    var c = 100
    c = a + b
    return c
}

func foo(param %a int, param %b int) int {
    local %c int
    set %c, 100 int
    %0 int = get %a
    %1 int = get %b
    %2 int = add %0 int, %1 int
    set %c, %2 int
    %3 int = get %c
    ret %3 int
}

klr_add_local
klr_add_global
klr_get_param
klr_build_get
klr_build_set
*/
void test()
{
    KlrModule *m = klr_create_module("test.kl");
    KlrType *param_types[] = { klr_type_int32(), klr_type_int32() };
    KlrType *func_type = klr_type_proto(klr_type_int32(), param_types, 2);
    KlrValue *func = klr_add_func(m, func_type, "foo");
    KlrBasicBlock *bb = klr_append_block(func, "entry");
    KlrBuilder bldr;
    klr_builder_end(&bldr, bb);

    KlrValue *param = klr_get_param(func, 0);
    klr_set_name(param, "a");
    KlrValue *var_a = klr_add_local(&bldr, klr_type_int32(), "a.ref");
    klr_build_set(&bldr, var_a, param);

    param = klr_get_param(func, 1);
    klr_set_name(param, "b");
    KlrValue *var_b = klr_add_local(&bldr, klr_type_int32(), "b.ref");
    klr_build_set(&bldr, var_b, param);

    KlrValue *cval = klr_add_local(&bldr, klr_type_int32(), "c.ref");
    klr_build_set(&bldr, cval, klr_const_int32(100));

    KlrValue *aval = klr_build_get(&bldr, var_a);
    KlrValue *bval = klr_build_get(&bldr, var_b);
    KlrValue *add = klr_build_add(&bldr, aval, bval, "add");
    klr_build_set(&bldr, cval, add);
    KlrValue *ret = klr_build_get(&bldr, cval);
    klr_build_ret(&bldr, ret);

    klr_print_func((KlrFunc *)func, stdout);
    klr_gen_ssa_form((KlrFunc *)func);
}

/*
func bar(a int) int {
    var b = 100
    var c = 0
    if (a > 20) {
        c = a + b
    } else {
        c = a - b
    }
    return c
}
*/
void test2()
{
    KlrModule *m = klr_create_module("test.kl");
    KlrType *param_types[] = { klr_type_int32() };
    KlrType *func_type = klr_type_proto(klr_type_int32(), param_types, 1);
    KlrValue *func = klr_add_func(m, func_type, "bar");
    KlrBasicBlock *bb = klr_append_block(func, "entry");
    KlrBuilder bldr;
    klr_builder_end(&bldr, bb);

    KlrValue *param = klr_get_param(func, 0);
    klr_set_name(param, "a");
    KlrValue *var_a = klr_add_local(&bldr, klr_type_int32(), "a.ref");
    klr_build_set(&bldr, var_a, param);

    KlrValue *var_b = klr_add_local(&bldr, klr_type_int32(), "b.ref");
    klr_build_set(&bldr, var_b, klr_const_int32(100));

    KlrValue *var_c = klr_add_local(&bldr, klr_type_int32(), "c.ref");
    klr_build_set(&bldr, var_c, klr_const_int32(0));

    KlrValue *aval = klr_build_get(&bldr, var_a);

    KlrValue *cond = klr_build_cmpgt(&bldr, aval, klr_const_int32(20), "cond");

    KlrBasicBlock *_then = klr_append_block(func, "if.then");
    KlrBasicBlock *_else = klr_append_block(func, "if.else");
    KlrBasicBlock *_end = klr_append_block(func, "if.end");

    klr_build_condjmp(&bldr, cond, _then, _else);

    klr_builder_end(&bldr, _then);
    aval = klr_build_get(&bldr, var_a);
    KlrValue *bval = klr_build_get(&bldr, var_b);
    KlrValue *cval = klr_build_add(&bldr, aval, bval, "");
    klr_build_set(&bldr, var_c, cval);
    klr_build_jmp(&bldr, _end);

    klr_builder_end(&bldr, _else);
    aval = klr_build_get(&bldr, var_a);
    bval = klr_build_get(&bldr, var_b);
    cval = klr_build_sub(&bldr, aval, bval, "");
    klr_build_set(&bldr, var_c, cval);
    klr_build_jmp(&bldr, _end);

    klr_builder_end(&bldr, _end);
    cval = klr_build_get(&bldr, var_c);
    klr_build_ret(&bldr, cval);

    klr_print_func((KlrFunc *)func, stdout);
    klr_gen_ssa_form((KlrFunc *)func);
}

/* "Engineering a Compiler" examples */
static void test3(void)
{
    KlrModule *m = klr_create_module("example-engineering-a-compiler.kl");
    KlrType *func_type = klr_type_proto(klr_type_void(), NULL, 0);
    KlrValue *func = klr_add_func(m, func_type, "example");
    KlrBasicBlock *bb0 = klr_append_block(func, "B0");
    KlrBasicBlock *bb1 = klr_append_block(func, "B1");
    KlrBasicBlock *bb2 = klr_append_block(func, "B2");
    KlrBasicBlock *bb3 = klr_append_block(func, "B3");
    KlrBasicBlock *bb4 = klr_append_block(func, "B4");
    KlrBasicBlock *bb5 = klr_append_block(func, "B5");
    KlrBasicBlock *bb6 = klr_append_block(func, "B6");
    KlrBasicBlock *bb7 = klr_append_block(func, "B7");
    KlrBasicBlock *bb8 = klr_append_block(func, "B8");

    KlrBuilder bldr;
    klr_builder_end(&bldr, bb0);
    klr_build_jmp(&bldr, bb1);

    KlrValue *cond = NULL;

    klr_builder_end(&bldr, bb1);
    cond = klr_build_cmpge(&bldr, klr_const_int32(2), klr_const_int32(5), "");
    klr_build_condjmp(&bldr, cond, bb2, bb5);

    klr_builder_end(&bldr, bb2);
    klr_build_jmp(&bldr, bb3);

    klr_builder_end(&bldr, bb3);
    cond = klr_build_cmpgt(&bldr, klr_const_int32(4), klr_const_int32(1), "");
    klr_build_condjmp(&bldr, cond, bb4, bb1);

    klr_builder_end(&bldr, bb5);
    cond = klr_build_cmple(&bldr, klr_const_int32(6), klr_const_int32(8), "");
    klr_build_condjmp(&bldr, cond, bb6, bb8);

    klr_builder_end(&bldr, bb6);
    klr_build_jmp(&bldr, bb7);

    klr_builder_end(&bldr, bb8);
    klr_build_jmp(&bldr, bb7);

    klr_builder_end(&bldr, bb7);
    klr_build_jmp(&bldr, bb3);

    klr_builder_end(&bldr, bb4);
    klr_build_ret_void(&bldr);

    klr_print_func((KlrFunc *)func, stdout);
    klr_gen_ssa_form((KlrFunc *)func);
}

int main()
{
    test();
    test2();
    test3();

    List list = LIST_INIT(list);
    list_push_back(&list, &list);
    HashMap map;
    hashmap_init(&map, NULL);
    Vector vec;
    vector_init_ptr(&vec);
    log_set_level(LOG_WARN);
    log_debug("hello");
    log_info("hello");
    log_warn("hello");
    log_error("hello");
    expect(1 == 1);
    // panic("1 == 2");
    return 0;
}
